﻿using System;
using System.Xml;
using System.Xml.Serialization;
using System.Collections;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Collections.ObjectModel;
using System.Xml.Schema;
using System.Xml.XPath;
using System.IO;
using System.Text;
using System.Xml.Linq;
using System.Linq;
using System.Configuration;

using XmlVisualizer.Controllers;
using XmlVisualizer.Controls;
using XmlVisualizer.Logging;
using XmlVisualizer.DiagramDesigner;
using GuiLabs.Undo;

namespace XmlVisualizer.Models
{

    #region Actions

    class UpdateAttributesAction : AbstractAction
    {
        public UpdateAttributesAction(XElement element, DocumentChangedDelegate updateUIAction)
        {
            this.element = element;
            attributes = element.Attributes();
            this.updateUIAction = updateUIAction;
        }

        XElement element;
        IEnumerable<XAttribute> attributes;
        IEnumerable<XAttribute> oldAttributes;
        DocumentChangedDelegate updateUIAction;

        protected override void ExecuteCore()
        {
            oldAttributes = attributes;
            updateUIAction(this, new DocumentChangedEventArgs(XmlNodeType.Attribute, element, element));
        }

        protected override void UnExecuteCore()
        {
            element.RemoveAttributes();
            foreach (XAttribute att in oldAttributes)
            {
                element.SetAttributeValue(att.Name, att.Value);
            }
            updateUIAction(this, new DocumentChangedEventArgs(XmlNodeType.Attribute, element, element));
        }
    }

    class SetElementNamespaceAction : AbstractAction
    {
        public SetElementNamespaceAction(XElement element, String newNamespace, DocumentChangedDelegate updateUIAction)
        {
            this.element = element;
            this.currentNamespace = newNamespace;
            this.updateUIAction = updateUIAction;
        }

        XElement element;
        String currentNamespace;
        String oldNamespace;
        DocumentChangedDelegate updateUIAction;

        protected override void ExecuteCore()
        {
            oldNamespace = element.Name.NamespaceName;

            if (currentNamespace != oldNamespace)
            {
                ucElement.ChangeNamespace(element, currentNamespace);
                updateUIAction(this, new DocumentChangedEventArgs(XmlNodeType.Element, element, element));
            }
        }

        protected override void UnExecuteCore()
        {
            ucElement.ChangeNamespace(element, oldNamespace);
            updateUIAction(this, new DocumentChangedEventArgs(XmlNodeType.Element, element, element));
        }
    }

    class SetElementNameAction : AbstractAction
    {
        public SetElementNameAction(XElement element, String newName, DocumentChangedDelegate updateUIAction)
        {
            this.element = element;
            this.name = newName;
            this.updateUIAction = updateUIAction;
        }

        XElement element;
        String name;
        String oldName;
        DocumentChangedDelegate updateUIAction;

        protected override void ExecuteCore()
        {
            oldName = element.Name.LocalName;

            if (name != oldName)
            {
                XNamespace ns = element.Name.Namespace;
                element.Name = ns.GetName(name);
                updateUIAction(this, new DocumentChangedEventArgs(XmlNodeType.Element, element, element));
            }
        }

        protected override void UnExecuteCore()
        {
            XNamespace ns = element.Name.Namespace;
            element.Name = ns.GetName(oldName);
            updateUIAction(this, new DocumentChangedEventArgs(XmlNodeType.Element, element, element));
        }

        public override bool CanUnExecute()
        {
            //return base.CanUnExecute();
            if (!String.IsNullOrEmpty(oldName))
                return true;
            else
                return false;
        }
    }

    #endregion

    /// <summary>
    /// This class manipulates with XDocument.
    /// </summary>
    public class XmlModel : IModel, IXmlModel, IXmlModelEvents
    {
        #region Properties
        /// <summary>
        /// Gets the XDocument.
        /// </summary>
        public XDocument Document
        {
            get { return document; }
        }

        /// <summary>
        /// Gets the XNodeCollection.
        /// </summary>
        public XNodeCollection Elements
        {
            get { return nodeCollection; }
        }
        #endregion

        #region Fields
        private XDocument document = null;
        private XNodeCollection nodeCollection = new XNodeCollection();
        private List<XElement> documentParts = new List<XElement>();
        private string xmlVisualizerNamespace = ConfigurationManager.AppSettings["xmlvisualizerNamespace"];
        private bool wellformed = true;
        private ActionManager actionManager;
        private XmlSchemaSet schemas = new XmlSchemaSet();
        private bool valid = true;
        private ElementChildCollection elementChildren;
        #endregion

        #region Constructors
        /// <summary>
        /// Initializes a new instance of the XmlModel class.
        /// </summary>
        public XmlModel(ActionManager actionManager)
        {
            this.actionManager = actionManager;
        }
        #endregion

        #region Events
        public event DocumentChangedDelegate DocumentChanged;
        public event Action<object, EventArgs> DocumentSaved;
        public event Action<object, EventArgs> DocumentLoaded;
        public event Action<object, EventArgs> DocumentLoading;
        public event Action<object, DisplayXmlEventArgs> DisplayXml;
        public event Action<object, EventArgs> DocumentClosed;
        public event Action<object, WellformednessEventArgs> DocumentWellformednessChanged;
        public event Action<object, NodeChangedEventArgs> NodeChanged;
        public event Action<object, SelectionChangedEventArgs> SelectionChanged;
        public event Action<object, XmvEventArgs> SaveXmv;
        public event Action<object, XmvEventArgs> OpenXmv;
        public event Action<object, ValidityEventArgs> DocumentValidityChanged;
        #endregion

        #region Event handlers
        /// <summary>
        /// Invoked when the XDocument changes.
        /// </summary>
        /// <param name="sender">The object where the event handler is attached.</param>
        /// <param name="e">The event data.</param>
        void document_Changed(object sender, XObjectChangeEventArgs e)
        {
            OnDocumentChanged(new DocumentChangedEventArgs());
        }

        /// <summary>
        /// Invoked when document changes.
        /// </summary>
        /// <param name="e">The event data.</param>
        protected virtual void OnDocumentChanged(DocumentChangedEventArgs e)
        {
            if (DocumentChanged != null)
                DocumentChanged(this, e);
            Validate();
        }

        /// <summary>
        /// Invoked when document gets saved.
        /// </summary>
        /// <param name="e">The event data.</param>
        protected virtual void OnDocumentSaved(EventArgs e)
        {
            if (DocumentSaved != null)
                DocumentSaved(this, e);
        }

        /// <summary>
        /// Invoked when document is loaded.
        /// </summary>
        /// <param name="e">The event data.</param>
        protected virtual void OnDocumentLoaded(EventArgs e)
        {
            if (DocumentLoaded != null)
                DocumentLoaded(this, e);
        }

        /// <summary>
        /// Invoked when document is loading.
        /// </summary>
        /// <param name="e">The event data.</param>
        protected virtual void OnDocumentLoading(EventArgs e)
        {
            if (DocumentLoading != null)
                DocumentLoading(this, e);
        }

        /// <summary>
        /// Invoked when is the right time to display the document.
        /// </summary>
        /// <param name="e">The event data.</param>
        protected virtual void OnDisplayXml(DisplayXmlEventArgs e)
        {
            if (DisplayXml != null)
                DisplayXml(this, e);
        }

        /// <summary>
        /// Invoked when document gets closed.
        /// </summary>
        /// <param name="e">The event data.</param>
        protected virtual void OnDocumentClosed(EventArgs e)
        {
            if (DocumentClosed != null)
                DocumentClosed(this, e);
        }

        /// <summary>
        /// Invoked when document's well formedness changes.
        /// </summary>
        /// <param name="e">The event data.</param>
        protected virtual void OnWellformednessChanged(WellformednessEventArgs e)
        {
            if (DocumentWellformednessChanged != null)
                DocumentWellformednessChanged(this, e);
        }

        /// <summary>
        /// Invoked when node's representation is updated.
        /// </summary>
        /// <param name="e">The event data.</param>
        protected virtual void OnNodeChanged(NodeChangedEventArgs e)
        {
            if (NodeChanged != null)
                NodeChanged(this, e);
        }

        /// <summary>
        /// Invoked when selection changes.
        /// </summary>
        /// <param name="e">The event data.</param>
        protected virtual void OnSelectionChanged(SelectionChangedEventArgs e)
        {
            if (SelectionChanged != null)
                SelectionChanged(this, e);
        }

        /// <summary>
        /// Invoked when model wants view to save xmv file.
        /// </summary>
        /// <param name="e">The event data.</param>
        protected virtual void OnSaveXmv(XmvEventArgs e)
        {
            if (SaveXmv != null)
                SaveXmv(this, e);
        }

        /// <summary>
        /// Invoked when model wants view to open xmv file.
        /// </summary>
        /// <param name="e">The event data.</param>
        protected virtual void OnOpenXmv(XmvEventArgs e)
        {
            if (OpenXmv != null)
                OpenXmv(this, e);
        }

        /// <summary>
        /// Invoked when document's validity changes.
        /// </summary>
        /// <param name="e">The event data.</param>
        protected virtual void OnDocumentValidityChanged(ValidityEventArgs e)
        {
            if (DocumentValidityChanged != null)
                DocumentValidityChanged(this, e);
        }
        #endregion

        #region Methods
        public void AddElementAddress(XElement element, ElementAddress address)
        {
            elementChildren.Add(element, address);
        }

        public void RemoveElementAddress(XElement element)
        {
            elementChildren.Remove(element);
        }

        public bool Validate()
        {
            document.Validate(schemas, (o, e) =>
            {
                Exceptions.ErrorHandling.ShowMessage(e.Message);
                valid = false;
                OnDocumentValidityChanged(new ValidityEventArgs(valid));
            });
            return false;
        }

        /// <summary>
        /// Loads the whole tree under the node (recursively).
        /// </summary>
        /// <param name="node">Node to load the info about.</param>
        /// <param name="depth">Node's depth in the tree.</param>
        /// <param name="order">Sequence number of the node if node is a child node.</param>
        public void Load(XNode node, int depth, ref int order)
        {
            if (node != null)
            {
                bool add = true;

                if (add)
                {
                    XNodeInfo info = new XNodeInfo(new Point(order * 160, depth * 120), depth, order, Properties.Settings.Default.defaultAppearance, node);

                    if (this.elementChildren != null && node is XElement)
                    {
                        if (elementChildren.ContainsKey(node as XElement))
                        {
                            info.ElementInfo = elementChildren[node as XElement];
                        }
                    }

                    AddNodeToCollection(node, info);
                }

                if (node is XElement)
                {
                    XElement element = node as XElement;
                    IEnumerable<XNode> nodes = from el in element.Nodes() where el is XElement || el is XText select el;

                    foreach (XNode n in nodes)
                    {
                        Load(n, depth + 1, ref order);
                        order++;
                    }
                    if (nodes.Count() > 0)
                    {
                        order--;
                    }
                }
            }
        }

        /// <summary>
        /// Calculates positions of nodes in document.
        /// </summary>
        public void CalculatePositions()
        {
            int d = 0;
            XNodeCollection elements = this.nodeCollection;

            int maxD = (from node in elements.Values where node.Depth == elements.Values.Max(c => c.Depth) select node.Depth).FirstOrDefault();

            d = maxD;

            IEnumerable<XNodeInfo> nodes = from node in elements.Values where node.Depth == d select node;
            while (nodes.Count() > 0)
            {
                int i = 0;
                foreach (XNodeInfo info in nodes)
                {
                    i++;
                    if (info.Node is XElement)
                    {
                        XElement el = info.Node as XElement;

                        if (el.Nodes().Count() > 0)
                        {
                            XNode first = el.Nodes().FirstOrDefault(c => c is XElement);
                            XNode last = el.Nodes().LastOrDefault(c => c is XElement);

                            if (first != null && last != null)
                            {
                                if (first != last)
                                {
                                    XNodeInfo firstInfo = elements[first];
                                    XNodeInfo lastInfo = elements[last];

                                    if (firstInfo != null && lastInfo != null)
                                    {
                                        info.Position = new Point((lastInfo.Position.X - firstInfo.Position.X) / 2 + firstInfo.Position.X, info.Position.Y);
                                    }
                                }
                                else
                                {
                                    if (first is XElement)
                                    {
                                        XNodeInfo firstInfo = elements[first];
                                        if (firstInfo != null)
                                        {
                                            info.Position = new Point(firstInfo.Position.X, info.Position.Y);
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
                d--;
                nodes = from node in elements.Values where node.Depth == d select node;
            }
        }

        /// <summary>
        /// Loads the xml document from the specified file location, then calls procedure to calculate
        /// default positions.
        /// </summary>
        /// <param name="filename">File to load the xml document from.</param>
        private void LoadXml(String filename)
        {
            XmlReader reader = null;
            try
            {
                XmlReaderSettings settings = new XmlReaderSettings();
                settings.ProhibitDtd = false;
                settings.IgnoreWhitespace = true;

                XDocument doc = XDocument.Load(filename);
                if (doc.DocumentType == null)
                {
                    settings.ValidationType = ValidationType.None;
                }
                else
                    settings.ValidationType = ValidationType.DTD;

                settings.ValidationEventHandler += new ValidationEventHandler(valReader_ValidationEventHandler);

                reader = XmlReader.Create(filename, settings);

                document = XDocument.Load(reader);
                LoadXml();
            }
            catch (XmlException ex)
            {
                Exceptions.ExceptionHandling.HandleException(ex);
            }
            catch (XmlSchemaException ex)
            {
                Exceptions.ExceptionHandling.HandleException(ex);
            }
            finally
            {
                if (reader != null)
                    reader.Close();
            }
        }

        void valReader_ValidationEventHandler(object sender, ValidationEventArgs e)
        {
            //this.valid = false;
            //OnDocumentValidityChanged(new ValidityEventArgs(valid));
            Exceptions.ErrorHandling.ShowMessage(e.Message);
            valid = false;
            OnDocumentValidityChanged(new ValidityEventArgs(valid));
        }

        private void LoadXml()
        {
            try
            {
                XNamespace schemaNamespace = XmlSchema.Namespace;
                XNamespace schemaInstanceNamespace = XmlSchema.InstanceNamespace;

                XAttribute val = document.Root.Attribute(schemaInstanceNamespace.GetName("schemaLocation"));
                if (val != null)
                {
                    string ns_location_raw = val.Value;
                    string[] ns_location = ns_location_raw.Split();
                    for (int i = 0; i < ns_location.Count(); i += 2)
                    {
                        schemas.Add(ns_location[i], ns_location[i + 1]);
                    }
                }
                else
                {
                    val = document.Root.Attribute(schemaInstanceNamespace.GetName("noNamespaceSchemaLocation"));
                    if (val != null)
                    {
                        string schemaLocation = val.Value;
                        schemas.Add("", schemaLocation);
                    }
                }
            }
            catch (XmlException ex)
            {
                Exceptions.ExceptionHandling.HandleException(ex);
            }
            catch (XmlSchemaException ex)
            {
                Exceptions.ExceptionHandling.HandleException(ex);
            }

            XElement firstPart = document.Root;
            AddNewDocumentPart(firstPart);

            int order = 0;
            Load(firstPart, 0, ref order);
            CalculatePositions();
            Logging.Log.AddMessage(VS.LoggerLevels.Info, "XmlModel", "Xml file loaded.");
        }

        public void LoadXml(XElement root, ElementChildCollection elementChildren)
        {
            try
            {
                this.elementChildren = elementChildren;
                if (document == null)
                {
                    document = new XDocument(root);
                }
                else
                {
                    document.ReplaceNodes(root);
                }
                LoadXml();
                OnDocumentLoaded(EventArgs.Empty);
                OnDisplayXml(new DisplayXmlEventArgs(nodeCollection, true));
            }
            catch (XmlException ex)
            {
                Exceptions.ExceptionHandling.HandleException(ex);
            }
            catch (XmlSchemaException ex)
            {
                Exceptions.ExceptionHandling.HandleException(ex);
            }
        }

        /// <summary>
        /// Callback for xml load completion.
        /// </summary>
        /// <param name="ar">IAsyncResult</param>
        private void XmlLoadedCallback(IAsyncResult ar)
        {
            if (ar.IsCompleted)
            {
                OnDocumentLoaded(EventArgs.Empty);
                OnDisplayXml(new DisplayXmlEventArgs(nodeCollection, true));
            }
        }
        #endregion

        #region Delegates
        private delegate void LoadXmlDelegate(String filename);
        #endregion

        #region IModel Members
        /// <summary>
        /// Loads the XML file into memory and tells the observers to show it. This is asynchronous action.
        /// </summary>
        /// <param name="filename">File location to open.</param>
        public void OpenFile(String filename)
        {
            try
            {
                string extension = Utils.StringUtils.GetExtension(filename);
                if (extension == ".xmv")
                {
                    OnOpenXmv(new XmvEventArgs(filename));
                    OnDocumentSaved(EventArgs.Empty);
                }
                else
                {
                    LoadXmlDelegate loadXmlDelegate = new LoadXmlDelegate(LoadXml);
                    AsyncCallback xmlLoaded = new AsyncCallback(XmlLoadedCallback);
                    IAsyncResult res = loadXmlDelegate.BeginInvoke(filename, xmlLoaded, loadXmlDelegate);
                    OnDocumentLoading(EventArgs.Empty);
                }
            }
            catch (XmlException ex)
            {
                Exceptions.ExceptionHandling.HandleException(ex);
            }
            catch (ArgumentException ex)
            {
                Exceptions.ExceptionHandling.HandleException(ex);
            }
            catch (PathTooLongException ex)
            {
                Exceptions.ExceptionHandling.HandleException(ex);
            }
            catch (DirectoryNotFoundException ex)
            {
                Exceptions.ExceptionHandling.HandleException(ex);
            }
            catch (FileNotFoundException ex)
            {
                Exceptions.ExceptionHandling.HandleException(ex);
            }
            catch (IOException ex)
            {
                Exceptions.ExceptionHandling.HandleException(ex);
            }
            catch (UnauthorizedAccessException ex)
            {
                Exceptions.ExceptionHandling.HandleException(ex);
            }
            catch (NotSupportedException ex)
            {
                Exceptions.ExceptionHandling.HandleException(ex);
            }
            catch (System.Security.SecurityException ex)
            {
                Exceptions.ExceptionHandling.HandleException(ex);
            }
            catch (NullReferenceException ex)
            {
                Exceptions.ExceptionHandling.HandleException(ex);
            }
        }

        /// <summary>
        /// Saves xml document.
        /// </summary>
        /// <param name="filename">File location to save to.</param>
        public void SaveFile(string filename)
        {
            string extension = Utils.StringUtils.GetExtension(filename);
            if (extension == ".xmv")
            {
                OnSaveXmv(new XmvEventArgs(filename));
                OnDocumentSaved(EventArgs.Empty);
            }
            else
            {
                if (wellformed)
                {
                    document.ReplaceNodes(documentParts[0]);
                    document.Save(filename);
                    OnDocumentSaved(EventArgs.Empty);
                }
                else
                {
                    throw new NotImplementedException();
                    //Document.Save(filename);
                    //OnDocumentSaved(EventArgs.Empty);
                }
            }
        }

        /// <summary>
        /// Gets the whole xml markup from the document.
        /// </summary>
        /// <returns>xml markup of the document as String</returns>
        public String GetXmlText()
        {
            try
            {
                if (wellformed)
                {
                    if (documentParts.Count == 1)
                    {
                        string text = document.ToString();
                        XDocument newDoc = XDocument.Parse(text);
                        newDoc.Root.Remove();
                        newDoc.Add(documentParts[0]);
                        return newDoc.ToString();
                    }
                    else
                    {
                        return String.Empty;
                    }
                    //return document.ToString();
                }
                else
                {
                    XElement element = JoinDocumentParts();
                    return element.ToString();
                }
            }
            catch (XmlException ex)
            {
                Exceptions.ExceptionHandling.HandleException(ex);
            }
            return String.Empty;
        }

        /// <summary>
        /// Closes document. Clears elements, XDocument, unhooks the event handler.
        /// </summary>
        public void Close()
        {
            foreach (XElement element in documentParts)
            {
                element.Changed -= new EventHandler<XObjectChangeEventArgs>(document_Changed);
            }

            Reset();

            OnWellformednessChanged(new WellformednessEventArgs(wellformed));
            OnDocumentClosed(EventArgs.Empty);
        }

        /// <summary>
        /// Removes node from its parent.
        /// </summary>
        /// <param name="node">node to remove.</param>
        /// <param name="recursively">indicates whether to remove whole tree under the node.</param>
        public void RemoveNode(XNode node, bool recursively)
        {
            try
            {
                RemoveNodeFromCollection(node);

                if (recursively)
                    if (node.Parent != null)
                        node.Remove();
                    else
                    {
                        if (node is XElement)
                            RemoveDocumentPart(node as XElement);
                    }
                else
                {
                    if (node is XElement)
                    {
                        XElement element = node as XElement;
                        IEnumerable<XElement> childElements = element.Elements();
                        foreach (XElement child in childElements)
                        {
                            XElement newElement = new XElement(child);
                            NodeChangeRec(child, newElement);
                            AddNewDocumentPart(newElement);
                        }

                        if (node.Parent != null)
                            node.Remove();
                        else
                        {
                            if (node is XElement)
                                RemoveDocumentPart(node as XElement);
                        }
                    }
                }

                OnDocumentChanged(new DocumentChangedEventArgs());
            }
            catch (InvalidOperationException ex)
            {
                Exceptions.ExceptionHandling.HandleException(ex);
            }
            catch (XmlException ex)
            {
                Exceptions.ExceptionHandling.HandleException(ex);
            }
        }

        public void SetElementName(XElement element, String name)
        {
            SetElementNameAction action = new SetElementNameAction(element, name, DocumentChanged);
            actionManager.RecordAction(action);
        }

        public void SetElementNamespace(XElement element, String newNamespace)
        {
            SetElementNamespaceAction action = new SetElementNamespaceAction(element, newNamespace, DocumentChanged);
            actionManager.RecordAction(action);
        }

        public void UpdateAttributes(XElement element)
        {
            //UpdateAttributesAction action = new UpdateAttributesAction(element, DocumentChanged);
            //actionManager.RecordAction(action);
        }

        /// <summary>
        /// Adds newElement to the child elements of the parentElement.
        /// </summary>
        /// <param name="parentElement">parent element</param>
        /// <param name="newElement">element to add</param>
        public void AddNewElement(XElement parentElement, XNodeInfo newElement)
        {
            XNode newNode = newElement.Node;
            AddNodeToCollection(newNode, newElement);

            if (parentElement != null)
                parentElement.Add(newNode);
            else
            {
                if (newNode is XElement)
                    AddNewDocumentPart(newNode as XElement);
            }

            // alter its position according to the left sibling (if there is one)
            // if not, leave it in the upper left corner...for now
            XNode leftSibling = newNode.PreviousNode;
            if (leftSibling != null && nodeCollection.ContainsKey(leftSibling))
            {
                XNodeInfo leftSiblingInfo = nodeCollection[leftSibling];
                newElement.Position = new Point(leftSiblingInfo.Position.X + 160, leftSiblingInfo.Position.Y);
            }

            XNodeCollection collection = new XNodeCollection();
            collection.Add(newNode, newElement);

            OnDocumentChanged(new DocumentChangedEventArgs());
            OnDisplayXml(new DisplayXmlEventArgs(collection, false, true));
        }

        /// <summary>
        /// Connect two nodes - add childNode as a child of parentNode.
        /// </summary>
        /// <param name="parentNode"></param>
        /// <param name="childNode"></param>
        public ConnectResult ConnectNodes(XNode parentNode, XNode childNode)
        {
            if (childNode.Parent != null && childNode.Parent.Name.NamespaceName != xmlVisualizerNamespace)
            {
                return ConnectResult.ChildHasParent;
            }

            if (parentNode is XElement)
            {
                XElement parentElement = parentNode as XElement;
                // group document part with parent document part
                if (childNode is XElement)
                { // delete document part with child element
                    RemoveDocumentPart(childNode as XElement);
                }
                XNode oldNode = childNode;
                parentElement.Add(childNode); // add child element to the parent element
                XNode newNode = parentElement.LastNode;
                if (newNode != null)
                {
                    //OnNodeChanged(new NodeChangedEventArgs(oldNode, newNode));
                    NodeChangeRec(oldNode, newNode);
                }

                return ConnectResult.Success;
            }
            else
            {
                return ConnectResult.ParentNodeNotElement;
            }
        }

        /// <summary>
        /// Select nodes in the current document.
        /// </summary>
        /// <param name="xpathExpression">XPath expression specifying what to select.</param>
        public void XPathSelection(string xpathExpression)
        {
            try
            {
                IEnumerable res = (IEnumerable)documentParts[0].XPathEvaluate(xpathExpression);
                IList<XObject> list = new List<XObject>();
                foreach (object o in res)
                {
                    if (o is XObject)
                        list.Add(o as XObject);
                }
                if (list != null && res != null)
                {
                    OnSelectionChanged(new SelectionChangedEventArgs(list));
                }
                if (list.Count == 0)
                {
                    MessageBoxes.ShowInformation("XPath.ZeroNodes");
                }
            }
            catch (XPathException ex)
            {
                Exceptions.ExceptionHandling.HandleException(ex);
            }
        }

        public void AddGuidAttributes()
        {
            foreach (XNodeInfo info in nodeCollection.Values)
            {
                if (info.Node is XElement)
                {
                    XElement element = info.Node as XElement;
                    XNamespace ns = xmlVisualizerNamespace;
                    Guid id = Guid.NewGuid();
                    element.SetAttributeValue(ns.GetName("ID"), id);
                    info.ID = id;
                }
                else if (info.Node is XText)
                {
                    XNamespace ns = xmlVisualizerNamespace;
                    XElement element = new XElement(ns + "text", info.Node);
                    Guid id = Guid.NewGuid();
                    element.SetAttributeValue(ns.GetName("ID"), id);
                    info.Node.AddBeforeSelf(element);
                    info.Node.Remove();
                    info.ID = id;
                    info.Node = element;
                }
            }
        }

        public void RemoveGuidAttributes()
        {
            foreach (XNodeInfo info in nodeCollection.Values)
            {
                if (info.Node is XElement)
                {
                    XElement element = info.Node as XElement;
                    if (element.Name.LocalName == "text")
                    {
                        String text = element.Value;
                        XText textNode = new XText(text);
                        info.Node.AddBeforeSelf(textNode);
                        info.Node.Remove();
                        info.Node = textNode;
                    }
                    XNamespace ns = xmlVisualizerNamespace;
                    element.SetAttributeValue(ns.GetName("ID"), null);

                    OnDocumentChanged(new DocumentChangedEventArgs(XmlNodeType.Attribute, element, element));
                }
            }
        }

        public XElement Serialize()
        {
            XElement root = new XElement("XmlModel");
            root.Add(new XElement("Document", GetXmlText()));
            root.Add(new XElement("DocumentParts", from part in documentParts select new XElement("Part", part)));
            return root;
        }

        /// <summary>
        /// Deserializes XmlModel part in Xmv file.
        /// </summary>
        /// <param name="root">root element for XmlModel part.</param>
        /// <exception cref="System.IO.FileFormatException">File is corrupted.</exception>
        public void Deserialize(XElement root)
        {
            ResetXmlModel();

            XDocument doc = XDocument.Parse(root.Element("Document").Value);
            if (doc == null)
            {
                ResetXmlModel();
                throw new FileFormatException();
            }
            else
            {
                document = doc;
                document.Changed += new EventHandler<XObjectChangeEventArgs>(document_Changed);
            }

            IEnumerable<XElement> parts = root.Elements("DocumentParts").Elements("Part");
            if (parts == null || parts.Count() == 0)
            {
                ResetXmlModel();
                throw new FileFormatException();
            }
            else
            {
                foreach (XElement part in parts)
                {
                    AddNewDocumentPart(part.Descendants().First());
                    part.Descendants().First().Remove();
                    //documentParts.Add(part.Descendants().First());
                    //part.Changed += new EventHandler<XObjectChangeEventArgs>(document_Changed);
                }

                if (documentParts.Count == 1)
                {
                    documentParts[0] = document.Root;
                }
                else if (documentParts.Count > 1)
                {
                    //XElement docRoot = JoinDocumentParts();
                    //document.ReplaceNodes(docRoot);
                }
            }
        }

        public XDocument GetDocument()
        {
            return document;
        }

        public IEnumerable<XElement> GetDocumentParts()
        {
            return documentParts;
        }

        public void AddNodeInfo(XNodeInfo info)
        {
            AddNodeToCollection(info.Node, info);
        }

        public void UpdateOrder(XElement parentElement, IEnumerable<XNode> childNodes)
        {
            parentElement.ReplaceNodes(childNodes);
        }

        public void Reset()
        {
            ResetXmlModel();
        }

        public void SetNamespacePrefix(XElement element, String prefix, XNamespace nameSpace)
        {
            element.SetAttributeValue(XNamespace.Xmlns + prefix, nameSpace.NamespaceName);
            OnDocumentChanged(new DocumentChangedEventArgs(XmlNodeType.Element, element, element));
        }
        #endregion

        #region Helper methods
        private XElement JoinDocumentParts()
        {
            XNamespace ns = xmlVisualizerNamespace;

            List<XElement> oldRoots = new List<XElement>();
            foreach (XElement elem in documentParts)
            {
                oldRoots.Add(elem);
            }

            if (oldRoots != null)
            {

            }

            XElement element = new XElement(ns + "parts",
                from el in documentParts
                select new XElement(ns + "part", el));
            return element;
        }

        /// <summary>
        /// Resets instance so new file can be loaded.
        /// </summary>
        private void ResetXmlModel()
        {
            documentParts.Clear();
            nodeCollection.Clear();
            wellformed = true;
            document = null;
        }

        /// <summary>
        /// Informs view that underlying XNode changed for certain XNode,
        /// this recursively goes down the tree so every node is updated.
        /// </summary>
        /// <param name="oldRoot">XNode associated with UI element so far.</param>
        /// <param name="newRoot">new XNode to be associated with the same UI element.</param>
        private void NodeChangeRec(XNode oldRoot, XNode newRoot)
        {
            OnNodeChanged(new NodeChangedEventArgs(oldRoot, newRoot));
            ChangeNode(oldRoot, newRoot);
            if (oldRoot is XElement)
            {
                XElement oldRootElement = oldRoot as XElement;
                XElement newRootElement = newRoot as XElement;

                List<XNode> oldNodes = new List<XNode>();
                foreach (XNode node in oldRootElement.Nodes())
                {
                    oldNodes.Add(node);
                }

                List<XNode> newNodes = new List<XNode>();
                foreach (XNode node in newRootElement.Nodes())
                {
                    newNodes.Add(node);
                }

                if (oldNodes.Count != newNodes.Count)
                {
                    throw new ArgumentException("counts mismatch");
                }

                for (int i = 0; i < oldNodes.Count; ++i)
                {
                    XNode oldNode = oldNodes[i];
                    XNode newNode = newNodes[i];
                    NodeChangeRec(oldNode, newNode);
                    ChangeNode(oldNode, newNode);
                }
            }
        }

        private void ChangeNode(XNode oldNode, XNode newNode)
        {
            if (nodeCollection.ContainsKey(oldNode))
            {
                XNodeInfo oldNodeInfo = nodeCollection[oldNode];
                oldNodeInfo.Node = newNode;

                if (oldNodeInfo.UI == null)
                    throw new ArgumentException("oldNodeInfo.UI is null");

                // create new XNodeInfo
                //XNodeInfo newNodeInfo = new XNodeInfo(oldNodeInfo.Position, oldNodeInfo.Depth, oldNodeInfo.Order, oldNodeInfo.AppearanceTemplate, newNode);
                //newNodeInfo.UI = oldNodeInfo.UI;

                //// update reference to XNode/XElement in UI item
                //if (oldNodeInfo.UI is DesignerItem) {
                //    DesignerItem designerItem = oldNodeInfo.UI as DesignerItem;
                //    designerItem.Node = e.NewNode;
                //    if (e.NewNode is XElement) {
                //        designerItem.Element = e.NewNode as XElement;
                //    }

                //}

                // update displayedNodes collection
                nodeCollection.Remove(oldNode);
                nodeCollection.Add(newNode, oldNodeInfo);
            }
        }

        /// <summary>
        /// Informs about changes in document's well-formedness.
        /// </summary>
        private void InformAboutWellformedness()
        {
            if (documentParts.Count <= 1)
            {
                wellformed = true;
            }
            else
            {
                wellformed = false;
            }

            OnWellformednessChanged(new WellformednessEventArgs(wellformed));
        }

        /// <summary>
        /// Adds new document part to the collection of document parts.
        /// </summary>
        /// <param name="root">Root element of the document part.</param>
        private void AddNewDocumentPart(XElement root)
        {
            if (document == null)
            {
                document = new XDocument(root);
            }
            root.Changed += new EventHandler<XObjectChangeEventArgs>(document_Changed);
            documentParts.Add(root);
            InformAboutWellformedness();
        }

        /// <summary>
        /// Removes document part from the collection of document parts.
        /// </summary>
        /// <param name="root">Root element of the document part which is about to be removed.</param>
        private void RemoveDocumentPart(XElement root)
        {
            if (documentParts.Contains(root))
            {
                documentParts.Remove(root);
                InformAboutWellformedness();
            }
        }

        /// <summary>
        /// Adds node to collection of nodes and corresponding XNodeInfo.
        /// </summary>
        /// <param name="node">XNode to add.</param>
        /// <param name="info">XNodeInfo associated with node.</param>
        /// <exception cref="System.ArgumentNullException">node or info is null.</exception>
        private void AddNodeToCollection(XNode node, XNodeInfo info)
        {
            if (node == null)
                throw new ArgumentNullException("node");
            if (info == null)
                throw new ArgumentNullException("info");

            nodeCollection.Add(node, info);
        }

        /// <summary>
        /// Removes node from collection of nodes and corresponding XNodeInfo.
        /// </summary>
        /// <param name="node">XNode to remove.</param>
        /// <exception cref="System.ArgumentNullException">node is null.</exception>
        private void RemoveNodeFromCollection(XNode node)
        {
            if (node == null)
                throw new ArgumentNullException("node");

            if (nodeCollection.ContainsKey(node))
            {
                nodeCollection.Remove(node);
            }
        }
        #endregion
    }
}